/*
 * mock.inc
 * vim: ft=c
 *
 * SPDX-FileCopyrightText: 2022-2025 BlueALSA developers
 * SPDX-License-Identifier: MIT
 */

#include <ctype.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "spawn.inc"

struct spawn_bluealsa_data {

	/* stderr from the BlueALSA server */
	FILE *f_stderr;

	pthread_mutex_t data_mtx;
	pthread_cond_t data_updated;

	char *dbus_bus_address;
	char *acquired_service_name;
	unsigned int ready_count_a2dp;
	unsigned int ready_count_midi;
	unsigned int ready_count_sco;

};

static char *strtrim(char *str) {
	while (isspace(*str))
		str++;
	if (*str == '\0')
		return str;
	char *end = &str[strlen(str) - 1];
	while (end > str && isspace(*end))
		end--;
	end[1] = '\0';
	return str;
}

static void *spawn_bluealsad_mock_stderr_proxy(void *userdata) {

	struct spawn_bluealsa_data *data = userdata;
	char buffer[512];
	char *tmp;

	while (fgets(buffer, sizeof(buffer), data->f_stderr) != NULL) {
		fputs(buffer, stderr);

		bool updated = false;

		pthread_mutex_lock(&data->data_mtx);

		if ((tmp = strstr(buffer, "DBUS_SYSTEM_BUS_ADDRESS=")) != NULL) {
			data->dbus_bus_address = strtrim(strdup(tmp));
			updated = true;
		}
		else if ((tmp = strstr(buffer, "BLUEALSA_DBUS_SERVICE_NAME=")) != NULL) {
			data->acquired_service_name = strtrim(strdup(&tmp[27]));
			updated = true;
		}
		else if (strstr(buffer, "BLUEALSA_READY=A2DP:") != NULL) {
			data->ready_count_a2dp++;
			updated = true;
		}
		else if (strstr(buffer, "BLUEALSA_READY=MIDI:") != NULL) {
			data->ready_count_midi++;
			updated = true;
		}
		else if (strstr(buffer, "BLUEALSA_READY=SCO:") != NULL) {
			data->ready_count_sco++;
			updated = true;
		}

		pthread_mutex_unlock(&data->data_mtx);

		if (updated)
			pthread_cond_signal(&data->data_updated);

	}

	pthread_mutex_destroy(&data->data_mtx);
	pthread_cond_destroy(&data->data_updated);
	free(data->dbus_bus_address);
	free(data->acquired_service_name);
	fclose(data->f_stderr);
	free(data);
	return NULL;
}

/**
 * Full path to the bluealsad-mock executable. */
char bluealsad_mock_path[256] = "bluealsad-mock";

/**
 * Spawn BlueALSA mock service.
 *
 * @param process Pointer to the structure which will be filled with spawned
 *   process information, i.e. PID, stdout and stderr file descriptors.
 * @param service BlueALSA D-Bus service name.
 * @param wait_for_ready Block until PCMs are ready.
 * @param ... Additional arguments to be passed to the bluealsad-mock. The list
 *   shall be terminated by NULL.
 * @return On success this function returns 0. Otherwise -1 is returned and
 *  errno is set appropriately. */
int spawn_bluealsa_mock(struct spawn_process *sp, const char *service,
		int wait_for_ready, ...) {

	/* bus address of D-Bus mock server */
	static char dbus_bus_address[256];

	unsigned int count_a2dp = 0;
	unsigned int count_midi = 0;
	unsigned int count_sco = 0;

	char arg_service[32] = "";
	if (service != NULL)
		sprintf(arg_service, "--dbus=%s", service);

	size_t n = 2;
	char * argv[32] = {
		bluealsad_mock_path,
		arg_service,
	};

	va_list ap;
	va_start(ap, wait_for_ready);

	char *arg;
	while ((arg = va_arg(ap, char *)) != NULL) {

		argv[n++] = arg;
		argv[n] = NULL;

		if (strcmp(arg, "--profile=a2dp-source") == 0)
			count_a2dp += 2;
		if (strcmp(arg, "--profile=a2dp-sink") == 0)
			count_a2dp += 2;
		if (strcmp(arg, "--profile=hfp-ag") == 0)
			count_sco += 1;
		if (strcmp(arg, "--profile=hsp-ag") == 0)
			count_sco += 1;
		if (strcmp(arg, "--profile=midi") == 0)
			count_midi += 1;

	}

	va_end(ap);

	if (spawn(sp, argv, NULL, SPAWN_FLAG_REDIRECT_STDERR) == -1)
		return -1;

	struct spawn_bluealsa_data *data;
	if ((data = calloc(1, sizeof(*data))) == NULL)
		return -1;

	pthread_mutex_init(&data->data_mtx, NULL);
	pthread_cond_init(&data->data_updated, NULL);

	data->f_stderr = sp->f_stderr;
	sp->f_stderr = NULL;

	pthread_t tid;
	pthread_create(&tid, NULL, spawn_bluealsad_mock_stderr_proxy, data);
	pthread_detach(tid);

	pthread_mutex_lock(&data->data_mtx);

	/* wait for system bus address */
	while (data->dbus_bus_address == NULL)
		pthread_cond_wait(&data->data_updated, &data->data_mtx);

	strncpy(dbus_bus_address, data->dbus_bus_address,
			sizeof(dbus_bus_address) - 1);
	putenv(dbus_bus_address);

	/* wait for service name acquisition */
	while (data->acquired_service_name == NULL)
		pthread_cond_wait(&data->data_updated, &data->data_mtx);

	while (wait_for_ready && (
				data->ready_count_a2dp < count_a2dp ||
				data->ready_count_midi < count_midi ||
				data->ready_count_sco < count_sco))
		pthread_cond_wait(&data->data_updated, &data->data_mtx);

	pthread_mutex_unlock(&data->data_mtx);

	return 0;
}
